/*
 * vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker:
 *
 * follow.c
 *
 * C.C.Allison
 * chris.allison@hotmail.com
 *
 * Started: Sunday 11 November 2012, 11:49:40
 * Version: 0.00
 * Revision: $Id: follow.c 54 2013-02-25 00:09:26Z chris.charles.allison@gmail.com $
 * Last Modified: Sunday 24 February 2013, 13:03:00
 */

#include "follow.h"

struct SummaryLine *createNewSummaryLine( long summarysize )/* {{{1 */
{
    struct SummaryLine *tsl;
    if(( tsl=malloc( sizeof( struct SummaryLine ))) == NULL ){
        CCAE(1,"Cannot allocate memory for summary line.");
    }
    if(( tsl->summary=malloc(summarysize)) == NULL ){
        CCAE(1,"Cannot allocate memory for summary line summary.");
    }
    // DBGL("summarysize: %ld",summarysize);
    tsl->linetime=0;
    tsl->linenumber=0;
    tsl->fpos=0;
    tsl->numstacks=0;
    return tsl;
} /* }}} */
void freeSummaryLine( struct SummaryLine *sl )/* {{{1 */
{
    if(sl->summary){
        free(sl->summary);
    }
    free(sl);
} /* }}} */
time_t is_logline(char *line, char *format)/* {{{1 */
{
    /*
     * parses the characters in line for a time string
     * matching format.
     * if it matches then it is converted to the epoch value
     * of that time, else returns 0.
     */
    struct tm *tim;
    time_t epoch=0;
    char *junk;

    /* syslog(LOG_DEBUG,"allocating space for time structure"); */
    tim=initTm();
    if(tim){
        /* syslog(LOG_DEBUG,"space allocated ok."); */
        junk=strptime(line, format, tim);
        if(junk){
            /* syslog(LOG_DEBUG,"strptime ok."); */
            epoch=mktime(tim);
        }else{
            /* syslog(LOG_DEBUG,"strptime failed:"); */
            /* syslog(LOG_DEBUG,"format: %s",format); */
            /* syslog(LOG_DEBUG,"line: %s",line); */
        }
        free(tim);
    }
    /* syslog(LOG_DEBUG,"return: %ld",epoch); */
    return epoch;
}/* }}} */
void resetFollowFile(struct FollowedFile *ffile)/* {{{1 */
{
    ffile->lastfilesize=0;
    ffile->linenumber=0;
    ffile->minstamp=INT_MAX;
    ffile->maxstamp=0;
    ffile->fpos=0;
}/* }}} */
struct FollowedFile *setupfollowfile(long fpos)/* {{{1 */
{
    struct FollowedFile *ffile=NULL;
    int needfree=0;
    long currentfilesize;
    char *filename;
    // char *tstr;

    filename=configValueDup("follow");
    if(filename!=NULL){
        if((currentfilesize=filesize(filename)) != -1 ){
            if((ffile=malloc((sizeof(struct FollowedFile))))){
                ffile->filename=filename;
                ffile->currentfilesize=currentfilesize;
                /*
                   ffile->lastfilesize=fpos;
                   ffile->searchfor=searchfor;
                   ffile->dateformat=dateformat;
                   ffile->linenumber=0;
                   ffile->bufsize=bufsize;
                   ffile->summarysize=summarysize;
                   ffile->sleeptime=sleeptime;
                   ffile->summarize=summarize;
                   ffile->fpos=fpos;
                   */
                ffile->lastfilesize=fpos;
                ffile->searchfor=configValueDup("search");
                ffile->dateformat=configValueDup("timeformat");
                ffile->linenumber=0;
                ffile->bufsize=strtolong(configValue("buffersize"));
                ffile->summarysize=(int)strtolong(configValue("summarysize"));
                ffile->sleeptime=(int)strtolong(configValue("sleeptime"));
                ffile->summarize=(int)strtolong(configValue("summarize"));
                ffile->minstamp=INT_MAX;
                ffile->maxstamp=0;
                ffile->fpos=fpos;
                ffile->linebuffer=malloc(ffile->bufsize + 1);
                if(ffile->linebuffer){
                    ffile->creg=init_regex(ffile->searchfor);
                    if(ffile->creg){
                        needfree=0;
                    }else{
                        needfree=1;
                        CCAE(1,"Cannot watch %s as there was an error initialising the regex /%s/.",ffile->filename, ffile->searchfor);
                    }
                }else{
                    needfree=1;
                    CCAE(1,"Cannot allocate memory for line buffer.");
                }
            }else{
                CCAE(1,"Cannot allocate memory for followed file structure.");
            }
        }
    }else{
        CCAE(1,"No file configured to follow");
    }
    if(needfree){
        ffile=freeffile(ffile);
    }
    return ffile;
}/* }}} */
void *freeffile(struct FollowedFile *ffile)/* {{{1 */
{
    if(ffile->creg){
        DBGL("calling free_regex %p",ffile->creg);
        free_regex(ffile->creg);
    }
    if(ffile->linebuffer){
        free(ffile->linebuffer);
    }
    if(ffile->searchfor){
        free(ffile->searchfor);
    }
    if(ffile->dateformat){
        free(ffile->dateformat);
    }
    if(ffile->filename){
        free(ffile->filename);
    }
    free(ffile);
    ffile=NULL;
    return ffile;
}/* }}} */
int followfile(struct FollowedFile *ffile)/* {{{1 */
{
    /* this assumes that the FollowedFile structure has been 
     * successfully filled in and is at a sane address
     *
     * This will follow the file named and emit summaries of the
     * regex matching lines.
     *
     * returns when the file has shrunk or the global var stopfollowing
     * comes true.
     *
     * returns the structure ffile member whystopped with the current state.
     * the structure member whystopped will be 1 if file has shrunk
     * (been rotated) or 2 if stopfollowing is active.
     * or <0 on error.
     */

    FILE *ffp;

    ffile->whystopped=0;
    while(ffile->whystopped == 0){
        if(stopfollowing){
            DBGL("following of %s has been interrupted.",ffile->filename);
            ffile->whystopped=2;
            break;
        }
        // DBGL("doing filesize of %s",ffile->filename);
        ffile->currentfilesize=filesize(ffile->filename);
        if(ffile->currentfilesize>-1 && ffile->currentfilesize>ffile->lastfilesize){
            /*
             * file has grown
             */
            DBGL("file has grown %ld bytes.",ffile->currentfilesize-ffile->lastfilesize);
            ffile->lastfilesize=ffile->currentfilesize;
            ffp=fopen(ffile->filename,"r");
            if(ffp){
                readThroughFFile(ffile,ffp);
                fclose(ffp);
            }
        }else{
            if(ffile->currentfilesize<ffile->lastfilesize){
                /*
                 * file has shrunk
                 */
                DBGL("File %s has shrunk. current: %ld last: %ld",ffile->filename,ffile->currentfilesize,ffile->lastfilesize);
                ffile->whystopped=1;
                break;
            }
        }/* end of if file size has changed */
        /*
         * going to break out now for testing
        ffile->whystopped=3;
         */
        sleep(ffile->sleeptime);

    } /* end of while(whystopped) */
    return ffile->whystopped;
}/* }}} */
int readThroughFFile( struct FollowedFile *ffile, FILE *ffp )/* {{{1 */
{
    /*
     * this assumes that the struct pointer ffile has been correctly initialised
     * and that the file pointer has been opened.
     */
    time_t linetime;
    int eatstacktrace=0;
    int matched=0;
    struct SummaryLine *sl;
    long holdfpos;

    if(ffp){
        /* seek to the correct position and count the lines as we go */
        if(ffile->fpos){
            holdfpos=ffile->fpos;
            ffile->fpos=0;
            while((linetime=nextLine(ffile,ffp))!=-1){
                if(ffile->fpos>=holdfpos){
                    break;
                }
            }
        }
        /* read the file */
        while((linetime=nextLine(ffile,ffp))!=-1){
            if(eatstacktrace==1 && matched==1){
                /* found an interesting line and skipping the stacktrace (if any) */
                if(linetime){
                    /* end of stack trace */
                    matched=0;
                    eatstacktrace=0;
                    emitdata(sl);
                    freeSummaryLine(sl);
                }else{
                    sl->numstacks++;
                }
            }
            if(eatstacktrace==0 && linetime){
                /* normal log line, check it against the regex */
                if((sl=checkLine(ffile))){
                    matched=1;
                    eatstacktrace=1;
                }
            }
        }
    }else{
        CCAC("Failed to open file %s for reading.",ffile->filename);
        ffile->whystopped = -3;
    }
    return ffile->whystopped;
}/* }}} */
int nextLine(struct FollowedFile *ffile, FILE *ffp)/* {{{1 */
{
    char *str;
    int ret=-1;

    str=fgets(ffile->linebuffer,ffile->bufsize,ffp);
    if(str==ffile->linebuffer){
        ffile->linenumber++;
        ffile->linetime=is_logline(ffile->linebuffer,ffile->dateformat);
        ret=ffile->linetime;
        if(ffile->linetime){
            if(ffile->linetime<ffile->minstamp){
                ffile->minstamp=ffile->linetime;
            }
            if(ffile->linetime>ffile->maxstamp){
                ffile->maxstamp=ffile->linetime;
            }
        }
        ffile->linelength=strlen(ffile->linebuffer);
        ffile->fpos+=ffile->linelength;
    }
    return ret;
}/* }}} */
struct SummaryLine *checkLine(struct FollowedFile *ffile)/* {{{1 */
{
    regoff_t matchpos;
    struct SummaryLine *sl=NULL;
    int sumsize;

    matchpos=do_regex(ffile->creg,ffile->linebuffer);
    if(matchpos>-1){
        /* a match was found */
        if(ffile->summarize){
            sumsize=ffile->summarysize;
            sumsize++;
            sl=createNewSummaryLine(sumsize);
            /* skip over the unmatched portion and retain the rest of the line */
            snprintf(sl->summary,ffile->summarysize,"%s",trim(ffile->linebuffer+matchpos));
        }else{
            sl=createNewSummaryLine(ffile->bufsize+1);
            /* copy the full line to output */
            strcpy(sl->summary,trim(ffile->linebuffer));
        }
        sl->numstacks=0;
        sl->linetime=ffile->linetime;
        sl->linenumber=ffile->linenumber;
        sl->fpos=ffile->fpos - ffile->linelength;
    }
    return sl;
}/* }}} */
